/*
 * This file is part of ELCube.
 *
 * ELCube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ELCube is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ELCube.  If not, see <https://www.gnu.org/licenses/>.
 */
package cn.nkpro.elcube.components.financial.services

import cn.nkpro.elcube.co.spel.NkSpELManager
import cn.nkpro.elcube.financial.NkAbstractFinanceMethod
import cn.nkpro.elcube.financial.NkFinanceMethodOption
import cn.nkpro.elcube.financial.NkFinancePaymentI
import cn.nkpro.elcube.utils.DateTimeUtilz
import cn.nkpro.elcube.utils.MathUtils
import com.alibaba.fastjson.JSONObject
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

import java.util.stream.Collectors

@Component("NkFinanceMethod01ZGCFixed")
class NkFinanceMethod01ZGCFixed extends NkAbstractFinanceMethod {

    @Autowired
    NkSpELManager spELManager

    @Override
    List<ProgramDef> programDefs() {
        return Arrays.asList(
             new ProgramDef("FIXED",         "固定金额",     true,true,true,false,false),
        )
    }

    @Override
    Object apply(Object json) throws Exception {

        Option option = new JSONObject(json as Map<String, Object>).toJavaObject(Option.class)

        if (option.validate()){
            Calendar calendar = Calendar.getInstance()
            calendar.setTimeInMillis(option.dt*1000)
            calendar.add(Calendar.MONTH, -option.tp)

            List<NkFinancePaymentI> payments = new ArrayList<>()
            for(int i=0;i<option.np;i++){

                NkFinancePaymentI item = new NkFinancePaymentI()
                item.setPeriod(i+1)
                item.setExpireDate(nextDate(calendar,option.step,option.dayOfPay))
                item.setRemark(null)

                def rule = option.getCustomPayAmountRule()
                        .find { r ->
                            r.program == 'FIXED' &&
                            r.activeDate <= item.expireDate &&
                                    (
                                            (r.effectiveDate && r.effectiveDate.length >= 2 && item.expireDate >= r.effectiveDate[0] && item.expireDate <= r.effectiveDate[1]) ||
                                                    (item.period >= r.effectiveFrom && item.period <= r.effectiveTo)
                                    )
                        }

                item.setPay(rule?.pay?:0)
                payments.add(item)
            }

            List<Double> values = new ArrayList<>()
            values.add(-option.pv)
            values.addAll(payments.stream().map{item->item.pay}.collect(Collectors.toList()))
            double rate = MathUtils.irr(values)?:0


            Double prevResidual = option.pv
            Long   prevExpireDate = option.dt
            for(int i=0;i<option.np;i++){
                NkFinancePaymentI item = payments.get(i)
                item.interest = doubleValue(prevResidual * rate)

                // 计算期初利息
                if(item.period == 1 && option.dateOfInterest > option.dt){
                    long dayCount = DateTimeUtilz.dateDiff(item.expireDate,prevExpireDate)
                    item.interest = doubleValue(calcInterest(option.rates,option.dateOfInterest,prevExpireDate,dayCount,prevResidual)+item.interest)
                }

                item.principal = Math.max(doubleValue(item.pay - item.interest),0d)
                item.residual = doubleValue(prevResidual - item.principal)
                item.pay = doubleValue(item.principal + item.interest)
                prevResidual = item.residual
                prevExpireDate = item.expireDate
            }

            clearLast(payments,option.fv)
            scale(payments, option.payPrecision, option.payRoundingMode)

            return payments
        }

        return null
    }

    static class Option extends NkFinanceMethodOption{
        @Override
        protected boolean customValidate() {
            return np != null && pf != null && pv != null && dt != null && pf!=0
        }
    }
}
